package com.ecmcloud.flow.service;

import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricActivityInstanceQuery;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.ecmcloud.common.constants.WorkflowConstant;
import com.ecmcloud.common.dto.PageResultDto;
import com.ecmcloud.common.dto.ProcessInstanceParamDto;
import com.ecmcloud.common.dto.TaskDto;
import com.ecmcloud.common.dto.TaskParamDto;
import com.ecmcloud.common.dto.TaskQueryParamDto;
import com.ecmcloud.common.dto.flow.Node;
import com.ecmcloud.common.utils.JsonMapperUtils;
import com.ecmcloud.flow.constants.WorkflowMediaType;
import com.ecmcloud.flow.utils.ModelUtil;
import com.google.common.collect.Lists;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class WorkflowService {

	@Autowired
    private TaskService taskService;
    
    @Autowired
    private HistoryService historyService;
    
    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private RuntimeService runtimeService;
    
    /**
     * 创建流程
     * @param nodeDto
     * @param userId
     * @return
     */
    public String create(Node nodeDto, String userId) {
        String flowId ="p"+ RandomUtil.randomString(9)+StrUtil.fillBefore(userId,'0',10);
        log.info("flowId={}", flowId);
        BpmnModel bpmnModel = ModelUtil.buildBpmnModel(nodeDto, "测试1", flowId);
        byte[] bpmnBytess = new BpmnXMLConverter().convertToXML(bpmnModel);
        // ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytess);
        String filename = "/tmp/flowable-deployment/" + flowId + ".bpmn20.xml";
        log.debug("部署时的模型文件：{}", filename);
        FileUtil.writeBytes(bpmnBytess, filename);
        repositoryService.createDeployment().addBpmnModel(StrUtil.format("{}.bpmn20.xml", "test1"), bpmnModel).deploy();
        return flowId;
    }
    
    /**
     * 发起流程
     * @param processInstanceParamDto
     * @return
     */
    public String start(ProcessInstanceParamDto processInstanceParamDto) {
        Authentication.setAuthenticatedUserId(processInstanceParamDto.getStartUserId());
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processInstanceParamDto.getFlowId(),processInstanceParamDto.getParamMap());
        String processInstanceId = processInstance.getProcessInstanceId();
        return processInstanceId;
    }
    
    /**
     * 根据流程id，显示流程图
     * @param procInsId
     * @return
     */
    public String showImg(String procInsId) {
        String procDefId;
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(procInsId).singleResult();
        if (processInstance == null) {
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInsId).singleResult();
            procDefId = historicProcessInstance.getProcessDefinitionId();
        } else {
            procDefId = processInstance.getProcessDefinitionId();
        }
        /**
         * 使用流程实例ID，查询正在执行的执行对象表，返回流程实例对象
         */
        BpmnModel bpmnModel = repositoryService.getBpmnModel(procDefId);
        /**
         * 创建默认的流程图生成器
         */
        DefaultProcessDiagramGenerator defaultProcessDiagramGenerator = new DefaultProcessDiagramGenerator();
        /**
         * 生成图片的类型
         */
        String imageType = WorkflowMediaType.MEDIA_PNG;
        /**
         * 高亮节点集合
         */
        List<String> highLightedActivities = Lists.newArrayList();
        /**
         * 高亮连线集合
         */
        List<String> highLightedFlows = Lists.newArrayList();
        /**
         * 查询所有历史节点信息
         */
        List<HistoricActivityInstance> hisActInsList = historyService.createHistoricActivityInstanceQuery().processInstanceId(procInsId).list();
        /**
         * 遍历
         */
        hisActInsList.forEach(historicActivityInstance -> {
            if (WorkflowConstant.SEQUENCE_FLOW.equals(historicActivityInstance.getActivityType())) {
                /**
                 * 添加高亮连线
                 */
                highLightedFlows.add(historicActivityInstance.getActivityId());
            } else {
                /**
                 * 添加高亮节点
                 */
                highLightedActivities.add(historicActivityInstance.getActivityId());
            }
        });
        /**
         * 节点字体
         */
        String activityFontName = WorkflowMediaType.FONT_NAME;
        /**
         * 连线标签字体
         */
        String labelFontName = WorkflowMediaType.LABEL_FONT_NAME;
        /**
         * 连线标签字体
         */
        String annotationFontName = WorkflowMediaType.FONT_NAME;
        /**
         * 类加载器
         */
        ClassLoader customClassLoader = null;
        /**
         * 比例因子，默认即可
         */
        double scaleFactor = 1.0d;
        /**
         * 不设置连线标签不会画
         */
        boolean drawSequenceFlowNameWithNoLabelDI = true;

        BpmnAutoLayout bpmnAutoLayout = new BpmnAutoLayout(bpmnModel);
        bpmnAutoLayout.setTaskHeight(120);
        bpmnAutoLayout.setTaskWidth(120);
        bpmnAutoLayout.execute();
        // 生成图片
        InputStream inputStream = defaultProcessDiagramGenerator.generateDiagram(bpmnModel,imageType,highLightedActivities,highLightedFlows,activityFontName,labelFontName,annotationFontName,customClassLoader,scaleFactor,drawSequenceFlowNameWithNoLabelDI); // 获取输入流
        String content = Base64.encode(inputStream);
        return content;
    }
    
    /**
     * 审批流程任务
     * @param taskId
     * @param approved
     */
    public void approve(String taskId, boolean approved) {
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("approved", approved);
        variables.put("ko", 10);
        variables.put("assigneeListSub", CollUtil.newArrayList("aa", "bb"));
        taskService.complete(taskId, variables);
    }
    
    /**
     * 结束流程实例
     * @param taskParamDto
     */
    public void stopProcessInstance(TaskParamDto taskParamDto) {
        List<String> processInstanceIdList = taskParamDto.getProcessInstanceIdList();
        for (String processInstanceId : processInstanceIdList) {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
            if (Objects.nonNull(processInstance)) {
                List<Execution> executions = runtimeService.createExecutionQuery().parentId(processInstanceId).list();
                List<String> executionIds = Lists.newArrayList();
                executions.forEach(execution -> executionIds.add(execution.getId()));
                runtimeService.createChangeActivityStateBuilder().moveExecutionsToSingleActivityId(executionIds,WorkflowConstant.END).changeState();
            }
        }
    }
    
    /**
     * 查询用户已办任务
     * @param taskQueryParamDto
     * @return
     */
    public PageResultDto<TaskDto> queryCompletedTask(TaskQueryParamDto taskQueryParamDto) {
        HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService.createHistoricActivityInstanceQuery();
        List<HistoricActivityInstance> list = historicActivityInstanceQuery.taskAssignee(taskQueryParamDto.getAssign()).finished().orderByHistoricActivityInstanceEndTime().desc().listPage((taskQueryParamDto.getPageNum() - 1) * taskQueryParamDto.getPageSize(),taskQueryParamDto.getPageSize());

        long count = historicActivityInstanceQuery.taskAssignee(taskQueryParamDto.getAssign()).finished().count();
        List<TaskDto> taskDtoList = Lists.newArrayList();

        for (HistoricActivityInstance historicActivityInstance : list) {
            String activityId = historicActivityInstance.getActivityId();
            String activityName = historicActivityInstance.getActivityName();
            String executionId = historicActivityInstance.getExecutionId();
            String taskId = historicActivityInstance.getTaskId();
            Date startTime = historicActivityInstance.getStartTime();
            Date endTime = historicActivityInstance.getEndTime();
            Long durationInMillis = historicActivityInstance.getDurationInMillis();
            String processInstanceId = historicActivityInstance.getProcessInstanceId();

            String processDefinitionId = historicActivityInstance.getProcessDefinitionId();
            //流程id
            String flowId = com.ecmcloud.flow.utils.NodeUtil.getFlowId(processDefinitionId);

            TaskDto taskDto = new TaskDto();
            taskDto.setFlowId(flowId);
            taskDto.setTaskCreateTime(startTime);
            taskDto.setTaskEndTime(endTime);
            taskDto.setNodeId(activityId);
            taskDto.setExecutionId(executionId);
            taskDto.setProcessInstanceId(processInstanceId);
            taskDto.setDurationInMillis(durationInMillis);
            taskDto.setTaskId(taskId);
            taskDto.setAssign(historicActivityInstance.getAssignee());
            taskDto.setTaskName(activityName);
            
            taskDtoList.add(taskDto);
        }
        PageResultDto<TaskDto> pageResultDto = new PageResultDto<>();
        pageResultDto.setTotal(count);
        pageResultDto.setRecords(taskDtoList);
        return pageResultDto;
    }
    
    /**
     * 查询用户待办任务
     * @param taskQueryParamDto
     * @return
     */
    public PageResultDto<TaskDto> queryAssignTask(TaskQueryParamDto taskQueryParamDto) {
        String assign = taskQueryParamDto.getAssign();
        TaskQuery taskQuery = taskService.createTaskQuery();
        List<Task> tasks = taskQuery.taskAssignee(assign).orderByTaskCreateTime().desc().listPage((taskQueryParamDto.getPageNum() - 1) * taskQueryParamDto.getPageSize(),taskQueryParamDto.getPageSize());
        long count = taskQuery.taskAssignee(assign).count();

        List<TaskDto> taskDtoList = Lists.newArrayList();
        log.debug("当前有" + count + " 个任务:");
        for (Task task : tasks) {
            String taskId = task.getId();
            String processInstanceId = task.getProcessInstanceId();
            log.debug("(" + taskId + ") " + task.getName() + " processInstanceId={} executrionId={}",processInstanceId, task.getExecutionId());

            Map<String, Object> taskServiceVariables = taskService.getVariables(task.getId());
            Map<String, Object> runtimeServiceVariables = runtimeService.getVariables(task.getProcessInstanceId());
            Map<String, Object> variables = runtimeService.getVariables(task.getExecutionId());
            log.debug("任务变量:{}", JsonMapperUtils.toJsonString(taskServiceVariables));
            log.debug("流程节点:{}", JsonMapperUtils.toJsonString(runtimeServiceVariables));
            log.debug("执行节点变量:{}", JsonMapperUtils.toJsonString(variables));

            //nodeid
            String taskDefinitionKey = task.getTaskDefinitionKey();

            String processDefinitionId = task.getProcessDefinitionId();
            //流程id
            String flowId =com.ecmcloud.flow.utils.NodeUtil.getFlowId(processDefinitionId);

            TaskDto taskDto = new TaskDto();
            taskDto.setFlowId(flowId);
            taskDto.setTaskCreateTime(task.getCreateTime());
            taskDto.setNodeId(taskDefinitionKey);
            taskDto.setParamMap(taskServiceVariables);
            taskDto.setProcessInstanceId(processInstanceId);
            taskDto.setTaskId(taskId);
            taskDto.setAssign(task.getAssignee());
            taskDto.setTaskName(task.getName());
            taskDtoList.add(taskDto);
        }
        PageResultDto<TaskDto> pageResultDto = new PageResultDto<>();
        pageResultDto.setTotal(count);
        pageResultDto.setRecords(taskDtoList);
        return pageResultDto;
    }
}
